"""
@-*- coding: utf-8 -*-
@ python：python 3.8
@ 创建人员:ylx-lingxiao
@ 创建时间:2024/11/20  23:04
@ File:study10.py
@ Explanation:selenium  web ui（user interface） 自动化 主要项目可以参考 uiframe0test项目
"""
"""
在企业中我们也不可能一直去做功能测试，我们学了Python这门语言，核心还是 如何能赋能到我们的实际工作中来，这也是我们为什么进行自动化测试学习的一个原因--目的就是为了业务去赋能的
整体来说就是2个方面，提升测试的效率，更好保证测试的质量，你学的东西最后实际都要落地到你的业务中去

整体的学习流程-自动化测试的简介-Python+selenium3.x的自动化测试环境搭建+常用元素定位工具介绍+selenium api学习+unittest框架介绍（Python中的一个单元测试框架）
+测试报告生成-自动化测试框架封装
这里的api 2层含义 一个是 我们调用某个方法获取我们想要的返回结果 第二个就是 我们前后端联系的一个枢纽-接口

我们目前所用的这个api指代的就是别人定义好的方法，我们如何去使用即可

UI自动化的本质---既你要去测试的场景，可以通过这5个关键词去覆盖
定位元素
操作元素
模拟页面动作
断言结果
生成报告

通过自动化代码的形式，一步步的把你之前手工测试操作的步骤，通过自动化代码实现了

UI自动化测试中不能跨步骤去执行，一定是一步一步的模拟你手工测试的步骤来完成的，不要跨步骤，写的时候多想想你的手工测试是怎么完成的
比如说你要测点击跳转-那你就应该先点什么 然后跳转 然后再点什么
要去页面底部确认，那你就应该先滑动至底部，然后再怎么操作

自动化代码也是基于手工用例去实现的


适合做自动化测试的场景--
需求不会频繁的变动--需求变动，导致手工用例一直更新，自动化测试无法展开
UI比较稳定--UI稳定了，做自动化才有价值，避免UI的改动造成控件布局的改动，从而导致代码的报错，维护成本高
项目周期长--代码还没写完，项目就要上线，测试时间就几天，那你这个阶段肯定是无法自动化测试的
有大量的回归测试任务--因为项目在持续迭代，你需要对之前的老功能做一些保障，随着迭代的增多，回归测试的任务也就逐渐多了起来
大量的重复任务，如果只用一次 就没必要写自动化，直接手动去点就好了，又直接还快速


自动化覆盖率--大概能达到，这个肯定是要结合项目来说的，国内的互联网企业都是小步快跑，快速迭代的一个过程，都走的是敏捷迭代，传统的v 瀑布 已经不适合现在的互联网行业
你可以这样回答，我们做的是一个什么项目，走的是敏捷迭代，我们是一个月一个版本的上线，所以我们的要求是把所有的p0级case 和核心的P1级case实现自动化
p0一般就是10-20，再多的话太重了--p0一定是你的核心主干功能
然后再加上p1级的核心主干 那总的来说，就是20-30之间

我们的覆盖率大概能达到20-30


我们还有一个项目，双周迭代，要求把所有的p0级case实现自动化，覆盖率15左右
再要是时间紧张一点，单周迭代，那我只是把核心的10来个场景实现自动化



再比如我们之前维护的一些政府或者国企的项目，这些项目比较稳定了，基本上不会有太多的迭代了，如果有，每次迭代的新功能也比较少，这样项目的覆盖率我们大概能达到50左右。（所以做自动化的目的就在于
这种时候你只需要测你的新功能，之前的回归测试完全交给自动化代码执行)
所以上方我们根据不同的项目特点来描述我们项目的自动化测试覆盖率

最终的说法：我们最近做的项目是---，我们走的是一个标准的敏捷迭代，双周迭代，我们之前因为每次迭代都要对老功能进行验证，所以我们要求把所有的
核心P0级别的case实现自动化，大概覆盖率能达到15左右-------所以最后怎么回答 ，总的来说 面试还是要落在项目上，结合项目的实际情况去描述


企业中做自动化测试或者执行自动化测试的2个场景


冒烟测试，一般来说很少做自动化对新功能的冒烟--新版本的核心主干功能，一般对国内来说很少这样做，主要还是因为时间太紧张了，如果这个项目周期本身就很长了，UI也非常稳定，那可能会在前期就把这些实现了

主要还是在   回归测试---我们开始版本的迭代了，1.1版本上线后那就要回归1.0版本，以此类推，随着后续版本的迭代，会产生大量的回归测试任务


什么时候去写自动化脚本，写自动化脚本的时机是什么时候呢
假设1.0上线 那么1.1版本处于这个开发的前期，或者你写测试用例的时期，然后1.1版本还没有转测的这个间歇期，那你就在这个间歇期，去写1.0版本核心主干功能的自动化脚本
因为你要在1.1版本 跑这个1.0版本的核心主干功能，做相应的回归测试

不适合做UI自动化的场景，比如说 视频播放器，你根本不知道它播放的是什么，UI自动化无法达成预期，所以，对这种交互性非常强的业务，我们不适合做UI自动化
检查页面的控件的颜色，检查图片，文字大小，视频，音频，等等，这些都是UI自动化无法实现的，你无法拿到准确的内容

我们做UI自动化，还是希望以基础的，交互的，功能去验证，比如说我点击登录，能否进入登录页面，输入用户名密码能否登录成功，能否搜索成功
就是这样基础的交互功能，我才能去实现自动化

















我们使用selenium去实现我们web端的UI自动化
selenium的特点
开源免费
多浏览器支持  firefox chrome  前个是最常用的浏览器     ie  edge opera
多平台支持  Linux  Windows Mac
多语言支持 Java Python js
对web页面有良好的支持--能支持web端几乎所有的项目
简单 灵活    api简单 用开发语言驱动


selenium经过多个版本的迭代，已经不再是一个由单独一个工具构成的，而是由一些插件，类库组成，每个部分都有其特点和应用场景，我们本次学习使用的是selenium 3.x版本
这是一个比较稳定的版本，基本上能覆盖我们所预期的相关功能



市面上的web ui自动化测试工具  
QTP   比较久远的一个工具
robotframework--关键字驱动   封装了selenium底层的api 定义成了自己的关键字 ，然后调这个关键字来实现对应的预期操作


我们最后还是想选择通过写代码去实现自动化，就是便于我们理解自动化测试的思想，代码你可以根据自己的需求去封装成你想要的各种形态


自动化测试效益：
之前每个版本 老功能回归5次 一次2h  
现在用自动化去完成了，那你就节约了10h   减去你写自动化脚本，维护自动化脚本的时间，就是你自动化的测试效益，一个是体现在时间，一个体现在质量，比如说发现多少问题等






Python+selenium3.x  自动测试环境搭建
配置环境变量，就是配置你可执行程序的路径的

1.安装Python
2.安装selenium   直接cmd下执行
pip install selenium == 3.14.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
这个默认安装在你系统变量的Python路径下了

pip list  查看 工具包
pip uninstall 包名 卸载哪个工具包

3.pycharm  file  setting   project(你新建的工程）   Python interpreter  选择你的Python.exe 这个就是你刚刚环境变量配置下的Python.exe 
因为后面是通过这个来编译你这个工程中的代码的
怎么选呢  齿轮  add  system interpreter   点击三个点 ，然后选择路径，然后你就能看到你这个版本的Python下 由这个selenium库了

一定不要选虚拟环境 virtualenv environment


我们通过pip安装的 都会在Python的 lib  site package下


4.下载和安装 chrome firefox 浏览器

5.对于Firefox 我们需要 单独下载geckodriver 并且这个还和Firefox的版本对应  一般都下载最新的就行
下载geckodriver 后需要把这个.exe放到Python的可安装路径下，就是跟你的Python.exe平行路径下，或者把geckodriver.exe添加至环境变量path中去即可
6.对于chrome  需要下载chromedriver  要和chrome的版本一一对应，如果没有，那么就选择一个接近的即可。如果不行，那就换比它大的一个版本
Windows电脑下载这个，不区分64  32 
下载解压后，把对应的chromedriver.exe 放在Python的安装路径下，也就是跟你的Python.exe平行的路径



selenium中常用api介绍-8大定位方式，用来定位你页面中的控件

ID一定是唯一的，name class不一定  name一般是唯一的   class有可能会有多个
但是使用的时候一定要注意刷新网页后这个id变不变，有的网站可能做了防护，导致会有变化


1.driver.find_element_by_id----无限制，有属性就能用
标签中有id属性时，表现为id = ''

通过标签中的id属性来定位这个控件--在html中，ID在一个页面中是唯一的
from selenium import webdriver  ------我们通过pip命令安装了selenium 而selenium的底层的核心就是webdriver协议，所以我要先将这个类导入进来，我才能更好使用这个类下的方法

driver = webdriver.Chrome() ---实例化对象，我实例化了webdriver中可以打开操作chrome浏览器的一个对象
driver.get('项目地址')   ----对象名.方法名 去调用这个相应的方法，如当前调用了get方法
driver.find_element_by_id('标签中，id的属性值').click()   ---前面是定位，后面是相关的动作

如果你这个控件由id属性，我们就可以通过上面的方法来定位这个控件

代码开始执行后
首先后台会去启动一个chromedriver的一个进程，这个进程中会包含一个selenium server，后面比如说你要打开这个浏览器，你会发起一个请求到这个
selenium server      server会去监听这个请求，知道你要去干什么，由selenium sever 去告知浏览器具体操作的一个动作，浏览器操作完成后，会把结果返回
给selenium server，然后server 会把这个结果给你展示出来或者说进行下一步的操作等，会把这个结果告知给发起请求的一方






2.driver.find_element_by_name ----无限制，有属性就能用
标签中有name属性时，表现为 name = ''

from selenium import webdriver

driver = webdriver.Chrome() 
driver.get('项目地址')   

driver.find_element_by_name('name属性的值').send_keys('输入框中你想输入的内容')







3.driver.find_element_by_xpath
在xpath中有绝对路径定位，从根节点开始，一层一层的往下找
第一种方式从上往下找，第二种方式从目标往根部找，第三种方式，下方直接看它的绝对路径，第四种 看箭头朝下的，展开的
/起始，开始写绝对路径
有/来分隔父子级关系

/html/body/div/div/div/a/div/span            如何进入浏览器底部写这个路径  Ctrl +f 即可这之前先确保你 点击了下这个elements区域
出现后你就可以在这里去写你的层级关系了


xpath的排序 是相同的在一起排序


在浏览器底部写完xpath路径后可以回车验证一下，看是否准确定位 如果是1of1 那你就写对了，一定要先确保唯一定位
如果下面有2个input标签的，你要选第1个 那你就加个比如说/input[1]     因为xpath的下标是从1开始的
所以说，如果路径是一样的，那你就要区分1 2 了，如果不一样，是唯一的，那就不需要区分了--这个一定要记住，就是看是否唯一性的路径，如果不，一个下面有多个
的，那你就需要选择

再举个例子，比如说你要找你2爸下的孩子，但是你爷爷下面有2个儿子，这2个儿子下面都有2个孩子，你要找的时候，如果不区分就会出现重复的路径，所以要用找哪个爸下的
就是这个意思----总而言之就是确保路径的唯一性

绝对路径是一定可以定位到的，但是缺点就是写的比较长，如果项目或者页面元素多，那你的路径就会非常的长，看起来不方便，写起来也不方便

driver.find_element_by_xpath('/html/body/div/div/div/a/div/span')



所以我们一般情况下，做自动化会使用--相对路径定位
相对路径就是你可以从某一级直接定位到这个控件
相对路径以//开始
//标签名[@唯一或者特有的属性='值']             也就是说只要是你这个控件的属性就可以去用，但是，只有特有属性才能完成目标定位，如果你选的是大家都有的，那就有可能出现重复
比如  //input[@class='but1']
 //input[@class='but1' and @name='key'] 当你一个无法定位时，我们可以使用2个甚至多个属性让他能够唯一化
如果标签内没有属性，比如目标仅仅是 <span>我的</span>，类似这种纯文本的我们还可以这样写
我们可以这样写//span[text()='我的购物车']
再比如我们还可以这样写
//*[text()='我的购物车']   这个*代表通配符，既我不管你是怎么样的标签，只要你的属性是这个就行（仅仅适用于相对路径下这样写）

对于有的网站属性会变的，比如说你虽然每次id是唯一的，但是你每次进来的时候，id都会变化
那你可以这样写//iframe[contains(@id,'属性中不变的那一部分值')]----包含关系的，可以去定位我们页面的动态元素。使用contains关键字
用包含关系去包含它不变的那一串字符串


像这种属性是一个链接地址的我们一般不要用，因为这些地址可能后期是会有变化的，再如method=get这种属性也不要用，因为一个页面可能有多个
type类型的一般也不要用，因为也存在多个，还有style属性的

从中间开始定位
//div[@class='schbox']/form/input[1],这种就属于半路找到一个特有属性的，然后你再逐个的去定位

从儿子找爸的
//input[@class='but1']/..  就可以找打它的上一级

从儿子找爷爷
//input[@class='but1']/../..  就可以找打它的上一级


还可以右击这个标签，然后 copy xpath地址

4.driver.find_element_by_link_text----仅能定义a标签，完整的文本内容定位
标签中的文本内容，比如说单纯一个a标签，后面是href的地址，再没有其它的属性值，然后就是文本，此时要定位，那么你取的就是标签中间夹的文本内容，
对于这种超文本标签，我们可以直接通过link_text 去定位--目前这里仅仅支持a 标签 ，用于查找包含特定文本的超链接  <a href = '跳转地址'>文本内容</a>
driver.find_element_by_link_text('文本内容')
如果重复，那么默认拿第一个


5.driver.find_element_by_partial_link_text----仅能定义a标签，部分文本内容定位，区别 link_text   partial_link_text 可以传部分的文本内容
如果要定位准确，你就要确保你这个部分文本内容，首先是唯一化，其次就是你需要一个连续的文本内容，因为它是连续匹配的

但是如果你想定位准确，那你传的这个部分文本内容一定要是页面唯一的，匹配的规则是连续的
这种场景一般在于，比如说文本内容很长，而且也有可能存在变量，会不断变化，只有一部分是固定的，那你就可以用这个定位方式

driver.find_element_by_partial_link_text('匹配文本值')



6.driver.find_element_by_class_name----无限制，有属性就能用
标签中有class属性的值是 表现为 class = ''
driver.find_element_by_class_name('标签中class的属性值')

标签中class 属性值 可能存在多个的情况，每个值中间使用空格间隔开，有空格的我们称其为复合类--由多个class属性值组成的
对于复合类我们不能一起用，用来去定位
我们只能选择其中的一个去定位
但是会存在一个问题，有可能会重复，所以一般碰到这种情况，我们可以选择其它的定位方式去定位






7.driver.find_element_by_tag_name---正常情况下不会使用这个方法去定位控件的，因为非常的不方便，element 只能找第一个 elements找的是全部的相同的控件
在当前页面上，可能有很多，所以你要区分你需要的是哪一个，就非常的麻烦
driver.find_element_by_tag_name('div')
通过这个方法去定位，找的是标签的类型
默认找的是第一个，弊端就是同样的组件会有很多次使用，你要找的不是第一个，那你就无法使用这个方法了




driver.find_elements_by_tag_name('div')  会拿到你当前页面的全部的div控件，返回一个列表




8.driver.find_element_by_css_selector-----一般不用，因为写起来比较复杂，有易用的地方比如定位 id 或者name时写起来比较方便，一般我们写路径都
使用xpath来完成
绝对路径
html>body>div>div>div>div>form>input:nth-child(1)   代表form下的第一个input标签
html>body>div>div>div>div>form>input:nth-last-child(1)   代表form下的倒数第一个input标签
html>body>div>div>div>div>form>input:first-child  代表form下的第一个input标签
html>body>div>div>div>div>form>input:last-child  代表form下的最后一个input标签
所以我们一般不使用css_selector 写绝对路径，因为语法比较繁琐，我们要是写绝对路径，我们就直接使用xpath来写绝对路径

相对路径
标签名#ID的属性值    也就是说你这个控件有ID属性值，你就可以这样写--只有id的属性值可以这样写
#id的属性值   也可以不写标签名，直接#id属性值，定位到你这个控件

标签名.class属性值
也可以直接.class属性值



没有以上这2个就正常写，比如

input[placeholder='文本值']

他和xpath的相对路径写法不同之处   xpath写法--- //input[@placeholder='文本值']   这是语法要求上的不同


如果有重复的,可以再加一组去写它的这个属性
input[name='key'][class='but1']

中间定位写法
div.schbox>form>input:nth-child(1)


这个方法不能从子找父的写法

如果你的父级下有多个子级，这个子级可能全部一样，可能有这个类型那个类型的，但是在这里我们是一起排序的，所以不看哪个类型几个 而是看全部的下面 你排第几个
css_selector 的排序是下面总的一起排序
这个form下有很多同级不同类型的标签 比如说有2个span 10个input这种，你的目标span是最后一个，那你就得这样来写
比如form#form>span:nth-child(12)>input














对于一个web页面核心的三部分
html   页面的布局，布局页面的，各种不同的标签，相当于家里不同的模块或者摆件，来表明这块的作用
javascript   页面的动作，比如说点击某个按钮后跳转，控制你页面的动作
css   来写页面布局的样式

对于web ui 自动化来说，我们要看页面布局的一个情况



在Python中 定位方式有find_element  和 find_elements 的区分
find_element  返回唯一的一个控件  默认第一个
find_elements  把你当前页面中所有相同类型的控件全部找到   比如你当前页面有2个name属性一样的控件，那你使用 find_elements_by_name  那你就把这2个
控件全部拿到了,这个找到的输出值你是看不懂的，你只能说你的目标是这些里边的第几个然后列表取值即可

elements的返回是一个列表

我们一般不用elements去找控件，因为我们一般只想唯一找到，所以一般都是find_element
所以一般不同elements
如果你要用，比如说你就要用，那name属性一样的是2个，你要第二个，那你可以用，因为数据量少，你也非常清楚的知道你要第几个，如果数据量很多，就不建议用这个





你了解哪些定位方式，你比较习惯或者喜欢用哪种，为什么

我比较喜欢用id定位或者name定位，因为id在一个页面是唯一的，name一般是唯一的，定位也比较简单方便
如果没有id或者name的时候，我一般习惯使用xpath定位，因为xpath基本上所有的控件都能定位到，整体语法也比较简单一些

link_text   partial_link_text  这些定位依赖a标签的文本，但是这个文本可能会变化，所以不好维护


至此，八大定位方式结束，下面我们来讲一些相关的操作



获取验证信息-2个api
获取title信息    title = driver.title     这个会返回当前页面的title值           因为使用了@property  这个装饰器使得其成为了一个属性方法，调用的时候不需要加括号
装饰器就是我在不修改当前方法任何代码的情况下，给了它一个新特性


获取跳转页面的url信息
先点跳转链接
driver.find_element_by_link_text('联系客服').click()
url = driver.current_url



模拟浏览器的行为
刷新  driver.refresh()
返回driver.back()
前进driver.forward()


控制窗体的大小
窗体最大化   driver.maximize_window()
模拟窗体固定的宽高  driver.set_window_size(宽，高)  默认是当前窗体



动作方法——先定位后在.方法
点击  click()
输入文本值 send_keys('文本值')
清空文本 clear()


属性方法的获取——先定位后在.方法
size  获取控件的大小，比如输入框的宽高  这个方法返回一个字典  格式为{'height':size['height'],'width':size['width']}
text  获取控件的文本内容 这个方法只能拿的是你这个标签中间夹的这个文本内容---这个文本内容在定位方式中 xpath  和 a标签的定位中 link_text   partial_text都用到了
get_attribute('属性名')  获取控件属性对应的值，这个方法会返回一个值


打开页面后，控件没有被加载出来，比如说网络慢，还在加载中，此时你的打开代码已经执行完毕了，此时要执行点击这个控件的操作
is_displayed()   判断定位的当前页面这个xpath路径下，这个控件是否加载出来了，返回一个布尔类型的，可以结合if或者while语句来做相应的判断及后续的操作
这个方法的前提是这个页面一定要有这个控件，无论是怎么定位的，有这个控件但是没有加载出来





鼠标事件--模拟鼠标的动作事件，相关的方法后都要.perform()去执行这个对应的动作
from selenium.webdriver.commom.action_chains import ActionChains

先定位
to_element = driver.find_element_by_link_text('母婴玩具')
time.sleep(4)
ActionChains(driver).move_to_element(to_element).perform()   移动至某个控件 用perform方法真正的去运行我前面所存储的动作
ActionChains(driver).context_click(to_element).perform()    右击某个控件
ActionChains(driver).double_click(to_element).perform()    双击某个控件



类中 self是类的一个实例化对象，指向类的本身，所以self也是类的一个对象，可以直接对象名.方法名去调用这个类中的方法
单纯的在某一个方法中定义一个变量，作用域仅在这个方法内，巧用这个__init__方法，初始化
使得其成为属性，可以在类中全局通用


拖目标位置控件至某个地方时松手    拓展的坐标 左上角是0.0点       x方向  右正左负      y方向  下正  上负
source = driver.find_element_by_xpath("")
target = driver.find_element_by_xpath("")
ActionChains(driver).drag_and_drop_by_offset(source,100,0).perform()            使用这个方法一般来说，拖拽的位置是固定的或者你滑动的距离是固定的

把一个控件拖拽到另一个控件的位置
ActionChains(driver).drag_and_drop(source,target).perform() 

跑自动化一般不要传固定值，比如说你的这个拖拽目标位置在你整体这个组件的1/2处，那你可以先前面的size方法或获取组件的宽高，然后再使用相关的位置就可以，避免兼容性的问题
比如说电脑屏幕尺寸的影响



键盘事件--模拟键盘中的按键，仅针对web端生效
from selenium.webdriver.commom.keys import Keys

比如说你要给一个输入框中输入内容，你首先要定位到这个控件，然后通过send_keys()来去决定输入的是什么，不确定使用哪个变量，你就Ctrl  keys这个类，看里边变量的定义
你如果想输入其它的键盘上的按键，我们都是通过send_keys(),然后keys.变量名即可
driver.find_element_by_xpath('').send_keys(Keys.ENTER)   输入回车
driver.find_element_by_xpath('').send_keys(Keys.BACK_SPACE)  输入删除-删除按键
driver.find_element_by_xpath('').send_keys(Keys.CONTROL,'a')   control a  组合按键

多联系手工测试时的步骤，比如说 你想全选 复制 粘贴
那你全选复制后 要点一些控件，否则就会覆盖，看起来没什么变化
这一切都是跟你的需求有关




三种等待时间
因为web端的UI自动化会比较常用，我要等这个控件加载出来后，点击才有效果，可能网络慢还没加载出来，那你要去点控件就会报错
我们加等待时间的目的就是想让控件出现，并且我们能够完成点击任务

1.强制等待时间，弊端就是效率较低，必须等够规定的时间，哪怕目标控件已经加载出来了，优势就是更加稳定一些
import time  使用了Python中的time模块

time.sleep(10)  等待10秒，这个方法的单位是秒，指程序在这里等10s，然后再往下执行

这里的时间根据你测试的结果来展示，比如说你测试了3次，那我们就取最慢的那次，确保代码可以正常执行

在最慢的基础上再加1-2秒的一个冗余时间，确保控件加载出来了


import time
from time import sleep 的区别
首先使用第一种，我们要等待时间的话要写 time.sleep() 如果是第二种 那我们可以直接sleep()\
第一种相当于导入整个time模块 第二种仅仅是导入了time模块中的sleep方法


2.隐式等待时间，优势就是效率高，这是一个全局的，比如说你初始化driver完成后，你加了隐式等待，那你后续比如说有2个操作
那你这2个操作，在定位控件之前一定会都用这个隐式等待时间，这个隐式时间的初衷就是等你当前html页面资源加载完成后，它才执行后续的操作
缺点--比如说你点了页面，页面没动或者没有响应，或者说反应慢，还没跳转过去
那对当前这个html页面，元素就是全的，所有元素都是加载出来的，
但是对隐式等待时间就是看你这个当前页面上的元素是不是完全加载出来的，有没有完全加载出来
那这样的情况下，由于你的反应慢，导致隐式等待时间它认为你已经进行了跳转，导致后续定位的错误
driver.implicitly_wait()
如果我们写了10，但是当前页面的加载只用了2秒的时间，那它只等2s，就执行后续步骤，不会把剩余的时间等完

它的生命周期起始于 driver 初始化开始，结束于driver.quit()



3.显示等待时间--效率高，比强制和隐式等待时间的效率都高，显式等待时间等的是局部的某个控件，隐式等待时间等的是当前html中所有控件加载出来
弊端-很快就开始去找了，如果点了页面，页面响应慢，或者还没有跳转，一旦在这个时间内，有和你目标路径一样的，或者跟你判断一样的
那就默认找到了，但是其实不是你的预期路径，因为页面都没跳转

必须要导入三个类
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

ele = WebDriverWait(driver,10,0.5).until(EC.presence_of_element_located((By.XPATH,'xpath对应的属性值')))
最大等10秒 每隔0.5秒去检测一次，直到当前页面加载出来 xpath是这个玩意的控件

自动化测试的过程中，稳定性大于效率，避免使用隐式或者显式等待时间导致报错，那这样的纠错就比较麻烦，要找到相应的平衡点

只要页面跳转一般都要用到等待时间，因为页面控件的加载需要时间
不重新渲染的，不重新跳转的，不需要加等待时间



多窗体切换
1.定位新窗体的控件

from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.maximize_window()
driver.find_element_by_id('').send_keys('')
driver.find_element_by_id("").click()

print(driver.window_handles)  打印你的所有窗体信息，获取我们目前所有的窗体信息

time.sleep(5)

driver.find_element_by_partial_link_text('').click()

time.sleep(5)

print(driver.window_handles)

print(driver.current_window_handle)  获取你句柄所在的这个窗体的信息

driver.switch_to.window(driver.window_handles[1])  切换窗体的句柄

time.sleep(5)

driver.close()   关闭当前句柄所在的窗体
print(driver.current_window_handle) ---这里会报错，因为上一个句柄被关闭了，不会有打印，也就是说你把窗体关闭了，句柄不会自动给你切
driver.quit() 退出浏览器，关闭全部窗体




alert 处理
对于鼠标右击无法查看其属性的这种控件，我们称其为alert

常见的alert形态
1.有文本信息，有确定按钮
2.文本信息，确认按钮，取消按钮
3.文本信息+输入框+确认按钮+取消按钮

---关键是找到alert，确定这个是alert后再用下面的方法

想给alert文本框输入对应的内容
driver.switch_to.alert.send_keys('123')  输入文本
print(driver.switch_to.alert.text) 获取alert的文本值
driver.switch_to.alert.accept() 确认
driver.switch_to.alert.dismiss() 取消



--对于回归测试的一个理解
新需求bug的回归验证
做新需求，对老功能影响的回归验证









对当前页面截图-辅助我们在报错时，进行页面的一个分析，便于找到错误
先到目标页面后，然后再截图，保存为png格式的文件
driver.get_screenshot_as_file('C:/user/1.png')    存到哪个路径下并给这个文件命名     路径写时避免转义 可以\\或者r
driver.quit()

如果当前页面有alert，你是无法拿到窗体信息的，也就导致你无法使用这个方法去截图，使用的话会报错
如果你想截图2种方法  一个借助Python的  pil库
另外一个就是你把alert处理后，就可以使用这个方法了
alert页面 拿不到信息，定位控件都无法定位






iframe切换
你们做自动化过程中有遇到什么报错，这个需要你积累
如：nosuchelementexception 控件定位不到

控件定位不到，有哪些原因导致的
1.代码本身的问题，比如，路径写错了，或者定位的元素有动态元素导致的
2.等待时间不够，页面未加载出来，导致点击不到
3.多窗体导致的，定位的目标在新窗体，但是句柄在当前窗体
4.iframe没有切换导致的


一般第4个原因导致的，我们检查页面时，一般来说html是我们的根节点，但是这种情况下我们会发现有多个HTML
我们一般定位控件就是在这个html下的body中去定位的，但是针对一个页面有多个html，我们要想定位其它html中的控件就不行了

所以就是利用iframe这种形式 给你的html页面嵌套了另一层的html
所以定位这个iframe里边的控件的前提就是你需要切到这个控件中去

所以针对这种情况，我们需要先切到这个iframe中，然后再定位这个里边的html控件
如何切iframe
如果你的这个iframe有ID或者name的情况下
1.直接通过driver.switch_to.frame('id或者name的属性值，并且这个值不是动态变化的')  注意这个值你需要有id或者name，并且值不能变，否则定位不到，运行会报错
2.先去定位到这个iframe,使用xpath，绝对或者相对路径
定位到后直接传进来，定位iframe
dd = driver.find_element_by_xpath('')
driver.switch_to.frame(dd)
成功切换后我们就可以用定位方式来定位这个iframe中的html控件了

多层iframe
你需要逐层去切，先切，然后再切
调一次切一次

定位完了，又想定位之前的iframe的，你就要往回切
driver.switch_to.parent frame()  从子iframe切回父iframe，这里也只能逐层往回切
driver.switch_to.default_content() 直接切回主文档，定位主文档中html的控件

从目前的开发实现来说，一个页面最多1-2个iframe，如果太多了，那就性能比较差了




下拉框的处理--如何选择下拉选择框中的内容

对于select标签的处理，及如何定位里边的option选项控件

我们使用如下方法
from selenium import webdriver
from selenium.webdriver.support.select import Select
import time




driver = webdriver.Chrome()
driver.get('')

s = driver.find_element_by_id('')
Select(s).select_by_visible_text('目标文本')  先定位到这个下拉选择框，然后，你选项的文本内容，也就是你option中间夹的这些文本
就是我想通过可见的文本内容去选择我想要的选项

Select(s).select_by_index(1)  通过下标去查找，它的下标是从0开始计算的

Select(s).select_by_value('chengdu') 通过你option标签对应的value值去查找你下拉框中的选项

但是如果你的目标控件不是select标签的话，则上述方法都不能用，对于不是select的，我们没办法使用select类

对于非select的，我们要去选择，我们仅仅需要按照正常的操作去进行代码编写即可，点开 展开后定位，定位后再点击即可，用常规的办法





时间控件的 处理
对于这种时间控件，首先它是一个input控件
对于普通的时间，为input控件的我们可以直接先定位到它
然后直接输入对应的时间即可

定位，清空默认时间
driver.find_element_by_id('').clear()
driver.find_element_by_id('').send_keys('预期时间，注意时间格式')



非普通时间空间，这个控件的标签会有一个readonly = 'readonly'的属性,如果时间控件有这个属性，你无法通过上面的方法直接输入进去
所以我们要先移除这个属性，然后再进行一个输入

移除readonly属性，利用js方法，先定义好js，然后执行js
js = 'document.getElementById('').removeAttribute('readonly')'  通过ID定位的方式，找到这个控件，也可以通过其它定位方式 比如说
document.getElementsByName[0]     document.getElementsByTagName[0]    但是这个就会取当前页面全部的，可能有类似相同的，需要切片取目标的，然后移除你对应的属性
对页面功能无任何影响，elements返回的是一个列表，你要访问列表中的内容，一定要加下标，然后定位到后，就说你要做的动作就行

driver.execute_scripts(js)  执行这段js代码
driver.find_element_by_id('').send_keys('预期时间，注意时间格式')



那有那么多一样的input你怎么知道你是哪个,可以写一段调试代码
a = driver.find_elements_by_tag_name('input')
for i in range(0,len(a)):
    try:
        if aa[i].get_attribute('特殊属性名')='特殊属性对应的值'   
        有的属性是属性名=属性值，有的仅有属性名
        有值的返回值，没有的返回None，如果仅仅看是否有这个属性，则返回布尔值
            print(i)
            break
    except:
        pass
        
        
        
滚动条处理
比如说你要拿页面底部的内容，你的手工操作就有，滚动至底部
所以我们要模拟滚动条的处理，他的核心还是我们调用js的方法
 
js = 'var q=document.documentElement.scrollTop=10000'    滚动的是像素点，如果你传递的值足够大，他就能滚动到底部   传0就会滚回来，传很大的值，就会滚到最底部
driver.execute_script(js)

driver.execute_script('window.scrollBy(0,-200)') 相对当前坐标移动到的坐标，相对于你当前滚动条的一个偏移

driver.execute_script('window.scrollTo(0,1500)') 移动至绝对坐标,不管你现在在哪里，我的目标就是这个位置，这个方法可以控制x 或y方向的滚动
driver.execute_script('window.scrollTo(0,document.body.scrollHeight*0.5)')   这个y坐标的值写的是 页高度的1/2，也就是可以定义移动到你页面高度的百分比位置
这个的好处就是你屏幕的分辨率再变，但你的效果是一样的

左上角是(0,0)点  y下正 上负   x 右正左负




懒加载/软加载
你拉至最底部的时候它才去加载下一页的数据，好处就是，如果一个页面打开全部加载，那你的页面性能可能会比较差，也就是我们说的分页加载
对于懒加载使用上面的比如说scrollTop这个方法也不行，可能你需要多次调用，你如果调一次，可能分页加载的页面，你是无法滚动至底部的









文件上传及验证码处理
文件上传的场景，一般有2种情况
input标签的文件上传
带有input标签并且type属性值为file的，可以直接使用send_keys上传文件，给一个你本地有的文件路径即可，把这个文件路径最好不要放在需要管理员权限的文件夹或者路径下
我们可以直接定位后然后使用send_keys方法  

非input标签使用AutoIt3上传----一般不会用到

面试问你，你就说，我们的项目大多数情况下，走的是标准的布局，这种上传的控件都是input类型的，我们直接使用send_keys
对于一些不是的，比较老的项目我们之前使用过AutoIt3来实现文件的上传，具体问你怎么实现的，你就说当时也是查资料的，用的也比较少，记不太清楚了



验证码的处理
我们之前的项目验证码是短信 或者这种字母信息的
像滑块或者手机验证码，我们一般是通过接口请求，拿到对应的验证码信息，其次就是让开发配合改为万能验证码，再要不然就是注入cookies

注入cookies的信息如何来实现--一般没人用，因为cookie会失效
我们首先通过自动回的方式打开我们的登录页面，加载一定的延时，手动登录后  打印cookies信息,把这个信息写入文件中

cookies = driver.get_cookies()
这样你之前的cookies信息就存在cookies的json文件中，下次要去使用读文件即可

上面就是你打开浏览器的过程

with.open() as file
cookiesinfo = json.loads(file.read())
for aa in range(len(cookiesinfo))
    driver.add cookie(cookiesinfo[aa])
driver.refresh()




所以面试问你你怎么说
我们之前的验证码信息都是接口返回的，我调接口信息返回后拿到这个验证码信息，然后塞到我的自动化脚本中
最早我们也做过拿cookie信息存到本地然后读取cookie信息塞入自动化脚本刷新浏览器的方式来完成
但是我们一般会让开发配合，用万能验证码，要么开发的验证码不做校验，我输入什么都可以通过，要么就是规定好哪个验证码通用，我们一般就是123456




我们前面学到的不管是理论还是环境搭建还是定位或者各种场景的api最后我们还是要落地到项目中去
我们的项目中我们怎么做自动化，把我们上面学到的东西柔和起来，去解决你实际项目中的各种场景
我们在公司去做自动化，我们的一个流程是怎么样的，第一步第二步干啥
我们一定要有一个目标或者范围，比如说我要实现哪些范围，哪些场景，哪些自动化的用例
这些都要根据你项目的实际情况，来决定你实现自动化范围的一个情况

先介绍你的项目，比如说我们之前做的是某某项目，我们项目的周期是一个双周迭代的过程，在做新需求时，存在对之前版本功能的一个回归，
每次手工回归的过程中，时间和效率也是比较差的，所以我们之前要求的是把之前老功能的所有的p0级case实现自动化
，我们需要去挨个过这些p0级的case，看是否能实现自动化，如果能的话，我们用例表中的执行方式就变更为自动

自动化用例从哪里来--- 所以我们的自动化用例一定来自你之前的手工用例，这是为了避免自动化一套用例，手工一套，那么就可能会出现重复的工作

以上的步骤做完后，我就清楚的知道了我要实现哪些用例为自动化

接下来就是搭建框架写代码实现自动化



UI自动化测试设计原则---
一个自动化脚本对应一个手工用例

一个脚本是一个完整的场景---这种就是对于项目速度迭代快，我们时间紧张，那么我们做的就是核心场景，这种模式的使用前提要么你的时间紧张，要么就是这个项目的场景化比较强

脚本之间互相独立，不能存在依赖---第一个脚本挂了不能影响后面脚本的执行，所以我们设计的重要原则就是每一个自动化脚本是一个可以独立运行的单元，每个脚本都要从头开始，不应该和其它脚本有依赖
可能这样效率比较低，但是自动化测试中，稳定性，大于效率

设置合适的检查点--就是一个断言结果，看你的预期和实际是否一致，所以我们的每一个自动化脚本中都应该有断言，去判断你的结果是否准确



设计良好的框架--本次我们使用的是Python中的unittest 单元测试框架



我们先来明确一下我们这个框架里应该有哪些东西
首先我们要有一个地方来放我们的自动化用例  case层  项目中，每一层用包的形式来管理，我们新建一个package，名为case，以此来管理我们的自动化用例，也就是我们自动化测试的脚本
放自动化用例的一个规则，比如说，我们一个项目肯定有非常多的模块
我们的自动化测试脚本的管理，也应该按模块来管理,一个模块就是一个py文件，一个py文件中存放你这个模块的全部自动化用例
比如说你要测试个人中心模块，那你就应该新建usercenter这个py文件，然后创建class TestUserCenter():这个类来管理你这个模块下的所有case




如何使用unittest单元测试框架
import unittest

class TestUserCenter(unittest.TestCase):                     我们定义的这个类要继承 unittest.TestCase这个类，我们的目的就是想要用继承的这个类中的方法
    
    @classmethod
    def setUpClass(self):                        这个先执行，开头只执行1次
        print('000')
        
    def setUp(self):
        print('111')
        
    def tearDown(self):
        print('222')
    
    def test01(self):                            这个方法必须以test开头，方法才能运行,我们使用unittest单元测试框架，这个test方法就是来存放我们的自动化测试用例的
        print('333')
        
    def test02(self):                            这个方法必须以test开头，方法才能运行
        print('444')

    @classmethod
    def tearDownClass(self):                   这个最后执行 结尾执行1次
        print('666')

虽然你只写了一次 setup  teardown  但是每次运行test开头的文件时 都会先运行setup  test  teardown    会先执行test01  再执行test02
如果你调换了他们的顺序，也还是按照这个执行
按照ascll码值来执行，从小到大依次为 0-9 A-Z a-z,所以在test方法的的执行中，是逐位比较的，小的先执行



setUp  setUpClass 的区别   从运行结果来看 setUpClass 方法会在类初始化的时候运行1次，这是类的一个初始化方法，或者说在你的类刚开始时执行时，运行1次
不管你这个里边有多少个test方法，它只运行1次  setUp是在你每个test方法执行之前都会运行1次

tearDown  tearDownClass 的区别同上



正常来说我们写一个类，在类中定义了方法，如果不调用这个方法是不会运行的，但是unittest框架有自身的特性，所以它会直接去运行


我们做ui自动化一般不会使用setUpClass  tearDownClass 这2个方法



time.time()  这个时间是相对于1970的时间  你肯定不认识 是一个151515454.36这种
time.localtime()  转换时间为本地时间       你肯定也不认识， 是一个time.struct_time(tm_year=2022,)的形式
time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime(time.time())) 格式化时间   2022-08-04-16-50-14  字符串格式


我们做自动化比如说登录，成功后会有一个toast提示，这个不是alert，alert是不会消失也无法定位到的，这个toast可以被定位到
且会消失

对于很快消失的控件我们该如何去定位呢？
点击source 然后再去点击登录让这个提示出现，然后点击右侧的暂停按钮即可
然后你再去element 再去定位就可以看到这个toast提示的属性了




你们的自动化测试是怎么落地的，请说明下
我们要把自动化落地的过程，你就要有非常清晰的理念，我要实现我这个项目的哪些部分去实现自动化，怎么去挑选我要实现自动化的内容
然后下一步才是我要去搭建框架，写代码等

我之前做的是一个云飞数字化智能平台，我们这个项目走的就是一个常规的敏捷迭代，2周一个迭代，我们在做新需求的过程中，经常要对
老版本的功能进行相关的回归验证，随着版本的发布，每次的回归测试中有大量的重复工作，比较耗费人力和时间
所以我要实现对应的P0级的case，然后在我们的测试用例中，加了一列叫执行方式，然后我会去过相关的手工用例，看能否实现自动化，可以的化
标记为自动，明确我要实现自动化用例的范围
接下来就是搭建框架和编写代码
new 一个project 然后分层使用package来管理每一层
第一层 我们放用例叫case层，我们以一个py文件放我们一个模块的所有的自动化用例
每个py文件中我们会定义一个类，这个类会继承unittest.TestCase,我们使用这个测试框架主要还是因为它包含三个部分
setup 一般我们做初始化的一些步骤，比如说初始化driver  打开目标地址等等    teardown  清理释放 driver.quit()，保证
每条用例都是独立的，互不影响
test  具体的手工用例执行






所以我们在做自动化的过程中，会把一些公用的业务流程封装起来，比如登录的过程，什么叫公用的业务流程，其实就是会被自动化case
多次用到的，比如说进入到某个模块的过程，也就是说如果你这个业务被其它很多case都用了，你就可以封装这个流程，后续相关使用的case
直接使用，避免大量重复写，导致一个地方变了，要修改多个地方
所以我们将公用的业务流程封装起来，如何封装？
我们一般会将公共的业务流程放在public层
新建一个Python文件
然后开始封装，你可以在case层直接调用这个类中的方法


class PublicFlow():
    def __init(self，driver):
        self.driver = driver   我们这里这样写的目的是因为 如果仅仅是driver自己  它的作用仅仅是这个方法内部，但是我们想让它作用于类中，成为类的一个全局变量
                                                                        我们就把它赋给self.driver，因为self是类的一个实例化对象，指向类的本身
    def login(self, name, passwd):
        self.driver.find_element_by_xpath('').click()        这里我们注意不能直接初始化driver 因为这个最后要用到case层，但是case层已经初始化完成了，你再初始化一个相当于要起2个不同的窗体了
        self.driver.find_element_by_name('').send_keys(name)       这里不建议写死，建议用变量                                         我们采取写一个__init__方法，让case层在调用我们时，必须传一个driver进来，保证我们用的是一个driver,这里可以参考鼠标事件
        self.driver.find_element_by_name('').send_keys(passwd)
        self.driver.find_element_by_xpath('').click()

    def customer_enter(self):
        相关操作
        
        
        
        
再比如我们还可以封装我们的八大定位方式，便于其他人理解和调用，对于一些没有做过自动化测试的人来说，理解起来更方便
我们可以对webdriver中的api进行二次封装，使得我们更容易理解和使用
我们一般把webdriver中相关api的二次封装放在common层,方便我们理解
我们可以封装八大定位方式  打开浏览器  切换iframe等具体的api


class common():
    def __init__(self,driver):
        self.driver = driver
        
    def find_element(self,byway,way_attribute):
        if byway = 'id':
            return self.driver.find_element_by_id(way_attribute)
        elif byway = 'name':
            return self.driver.find_element_by_name(way_attribute)
        
    def open_browser(self, url):
        self.driver.get(url)
        




断言方式

self.assertEqual(预期，实际） 相等
self.assertNotEqual(预期，实际）   不相等
self.assertIn(目标，实际文本)   看实际文本是否包含了目标内容  包含
self.assertNotIn(目标，实际文本)
self.assertTrue()  断言括号里的内容是否是True          判断某一个控件在当前页面是否加载出来了     .is_displayed()
self.assertFalse()断言括号里的内容是否是False,判断当前页面这个控件没有被加载出来


assertTrue  可以被assertEqual 替代，前面写True 后面是对应的is_displayed()方法
还有很多的断言方法，可以自己看源码，上面说的是比较常用的几种

不知道怎么写的断言我们还可以通过if  else 语句来完成，对于异常的，我们可以自己抛出异常
直接写一个错误的代码放在那里等报错，从而检查对应的错误，比如说写一个xpath路径根本不存在的driver.find_element_by_xpath('')

比如说你要查询一个数据，那你肯定要新建一个数据，怎么确保新建的唯一性，就是给后面加时间戳确保，每次测试都能跑通 完成业务的闭环
此时你再搜索 搜索到后验证了搜素功能和新增功能的准确性。不能仅仅依靠比如说前端代码的toast提示





全部的case写完了，此时你要运行了，
运行所有的自动化用例，比如说你有10个模块在你的case层，我想一次性运行所有的自动化代码
所以我们又封装了一个testrunner层来找和运行我们所有的自动化测试用例
+
测试报告

import unittest
import time
import os
from report.HTMLTestRunner import HTMLTestRunner   这是别人开发好的一个第三方的库，我们可以直接调用，我们把这个py文件放在了report路径下，所以这样引用


class run():
    def runtest(self):
    case_path = r".\case\web"   .就指你的当前路径，我当前把这个文件放在这个工程下，相对路径，避免兼容性错误，方便别人使用
    result_path = r"\\report\\"
    test_case  = self.create_suite(case_path)   因为self是类的实例化对象，所以self可以调用类中的方法，拿到所有的自动化测试用例
    now = time.strftime('Y%-m%-d%-H%-M%-S%',time.local(time.time()))
    day = time.strftime('Y%-m%-d%',time.local(time.time()))
    tdresult = result_path+day
    
    if not os.path.exists(tdresult):  如果你的report路径下没有今天的路径我就给你创建一个这样的路径
        os.makedirs(tdresult)
        
    
    filename = tdresult + '\\'+now+'_result.html'  存放文件的路径+文件的名称
    fp = open(filename,'wb')   给这个文件内开始写内容
    runner = HTMLTestRunner(stream=fp,title='',description='')
    runner.run(test_case)
    fp.close
    
    
    
    def create_suite(self,case_path):
        suites = unnitest.defaultTestLoader.discover(
        start_dir = case_path,
        pattern = '*.py',
        top_level_dir = None
        )
        return suites
        
        
if __name__ == "__main__": 相当于我这个类的入口函数，意思就是我从这里开始执行
    run().runtest()     


from的引用是从当前工程的路径下开始找的






运行的结果 E 就是error  . 代表通过     F 代表断言失败了
test方法中下方用三个双引号引起来的内容 后面会体现在测试报告中，明确这个用例是干什么的


我们最后运行的时候一定是运行这个py文件的，有时候编译器会让你用unittest这个单元测试框架去运行，那肯定不行，解决方案是 
去掉所有的，然后运行时，选择+  选择Python  选择脚本路径，再去run就可以了
所以也就是说 要run  testrunner  只能用Python 去run 

你要run你case层中的一个脚本时 你要以run unittest 的方式去run   你也可以去掉所有 然后添加 Python tests  选择unittests
然后选择scripts/path  选择脚本即可
也可以module_name  写你的脚本名字  gouwuche.testgouwuche.testbugcar01_5    文件名.类名.方法名   运行这个里边的某一条case





答疑
1.输入手机号，后续会记住，这个不是web端的行为，是纯浏览器行为
2.遇到验证码，推荐就是开发配合调试为万能验证码
3.web端兼容性 chrome 或者Firefox  一般来说都是一样的，可能仅仅存在一些细微的不同，比如我们新建客户页面
chrome就会出现一个确定位置的alert 但是Firefox不会，我们采用try except的方法来捕获异常，捕获异常后直接 pass即可
再对于这个公共方法，可以直接将driver的初始化也封装到common层，这样的化后续初始化直接调用就行
4.py文件用英文，别用中文，就是你的模块名称，使用英文字符，中文不匹配
5.自动化用例哪里来的，你们大概有多少条自动化用例
我们的自动化用例来源于我们的手工用例，我们的项目走的是一个双周迭代，我们要求是把上一个版本的p0级case实现自动回，覆盖率大概在15-20左右
6.你们自动化测试流程是怎么样的？
介绍项目情况--为了对之前老功能进行一个回归验证，消耗人力成本和时间-所以我们要求把所有的P0级case实现自动化，我们会结合我们之前的
手工用例来过下我们目标范围内的P0级case，看哪些能实现自动化，形成我们最终要实现的目标case，然后我们进行框架搭建，我们框架核心分为
case层，一个模块对应一个py文件，我们主要还是继承了unittestTestCase来实现我们的自动化，setup  teardown  test方法
public层 封装公用的业务流程，比如登录等，避免后续修改影响较大
common层，我们封装了webdriver中api的二次封装，比如我们封装了8大定位方式，封装了打开浏览器，封装了相关api
report层，存放我们的测试报告
testrunner层，找到所有的测试用例并执行，主要依赖unittest.defaultTestLoader.discover方法和HTMLTestRunner.run方法


7.比如说我们要分析报错，一个定位不到的错误
我们先切换为Firefox，然后 将teardown中的quit先注释掉，方便我们看对应的错误页面
为什么要在Firefox中这样，因为即使你使用chrome 且注释了quit，但是浏览器还是会退出
然后右击检查，复制出错的xpath路径，来看是否能定位到，定位不到然后检查目标控件的路径


我们在自动化的过程中不要跨步骤，一定要想如果是手工测试我们是怎么做的，比如说新建的时候我们先写了客户级别 那这个时候你定位的，跟你这页面都写完
再定位肯定是不一样的，因为层级更深了，所以做自动化的时候，一定不要跨步骤，报错要会分析





8.自动化执行一次需要多久，是否每次都要去执行？
铺垫，结合你的项目说，你的自动化用例个数，快的多少，慢的多少，平均可能一次就是1个小时左右，一般我们也是晚上跑，具体的时间到还没有专门关注过
我们一般冒烟测试通过后，基本上每天晚上都会去执行我们相关的自动化测试用例，实时的去监控新需求是否对老需求有影响
但是对于有的迭代，可能改动的影响范围比较小，我也可能只跑某个模块



9.自动化测试中的数据是怎么来的，比如说你客户列表的测试数据
我们要么自己新建数据，在测试环境中。每次的测试新建一个有特征的数据，尾部带时间戳，
或者直接Python操作数据库批量插入数据
或者我们先新建数据然后再编辑数据
反正我们就是要确保我们的目标数据是唯一的，是我们能去使用的

10.某个控件有时定位Ok 有时不行
1.等待时间不够
2.网络抖动，导致控件还没加载出来
3.页面数据产生的变化，比如说数据被别人删除或者修改----------比如说新建客户页面 数据的多少 导致相关保存按钮的层级不对，所以有时能定位到 有时不行
怎么办呢，我们应该更换定位方式，看是否能解决这个问题
4.新需求修改，导致老功能受到了影响，出现了问题




对于比如说动态元素对比的，获取值后一定要赋值给变量，把值存起来，避免点击后页面发生变化，然后后续再想对比时，值拿不到了


对于断言，一定是断言你手工用例的内容，而不是说单纯的为了自动化而断言，对于手工用例有的页面我们一看就是对的，但是对于自动回
就要对比一些有特殊地方的内容去判断

从你整个学自动化的过程中，我们为什么要去讲自动化的理念或者概念
你需要清楚在什么场景去做这个，怎么做，自动化能不能在这个项目中去做，做的场景是不是适合，然后才说我怎么去写代码
写代码的过程中
你要描述清楚你环境搭建的流程
然后自动化第一步，定位控件，执行操作，每层封装了什么，做什么事
遇到报错怎么分析，怎么校验
其实一般来说，最难的就是定位，要分析原因看是由于操作还是说控件没出现或者路径错误导致的，要分析清楚




跑完后我们一般就是去看测试报告，看执行的情况和一些错误的分析





webui自动化 PO 模式
1.传统的实现方式，我们之前学的
2.PO模式，为了就是解决降低，对于测试套件持续增加，以及脚本变多后维护的复杂度，避免页面任何改变都会让我们的脚本维护变得繁琐而复杂
PO模式就是page object model 页面对象模型
作用就是把测试页面和测试脚本进行分离，把页面封装成类，供测试脚本使用
分层机制，让不同的层做不同类型的事情，使得代码的结构清晰，增加复用性
PO设计模式是selenium自动化测试中最佳设计模式之一，主要体现在对页面交互细节的封装

不使用PO模式，复用性不好，扩展性不高，易读性差，不好维护
UI页面繁琐的项目维护起来比较麻烦

优点
减少代码冗余
业务和实现分离
降低维护成本
提高代码的可维护性，尤其是针对UI繁琐的项目

脚本分为三层
基础层  封装一些selenium原生的方法，比如元素定位，框架跳转等   page层
逻辑层   元素定位  页面动作
业务层  数据驱动

业务层调逻辑层 逻辑层调基础层


@classmethod 修饰符对于的函数不需要实例化，直接可以类名.方法名 不需要self参数，直接调用类的属性或者方法


"""
